"""
课题库管理模块
"""
from flask import current_app, request
from flask_restx import abort, reqparse
from sqlalchemy import and_, or_
from werkzeug.exceptions import HTTPException

from app.common.range_manager import RangeManager
from app.models import (
    Ctf,
    CtfIssueBelong,
    CtfIssueType,
    CtfQuestion,
    CtfRedBlue,
    IssueInitialBindRelation,
    questionModel,
)
from tools import db
from utils.utils import base_detail

from .base_view import OrganizerBase


class Question(OrganizerBase):
    def getQuestionInfo(self, id):
        question_info = None
        if id:
            question_info = questionModel.CtfQuestion.query.filter_by(
                id=id, user_id=self.user.id, is_system=0
            ).first()

        return question_info


# ctf题库添加
class CtfQuestionManage(Question):
    def post(self):
        # ctf题库 增加
        parser = reqparse.RequestParser()
        parser.add_argument("name", help="请填写试题名称", required=True)
        parser.add_argument("type", help="请填写试题类型", required=True, type=int)
        parser.add_argument("score", help="请填写试题分数", type=int, required=True)
        parser.add_argument("answer", help="请填写试题答案", required=True)
        parser.add_argument("describe", help="请填写试题描述", required=True)
        parser.add_argument("vm_uuid", required=False)
        parser.add_argument("zip")
        parser.add_argument("zipname")
        args = parser.parse_args()

        if args["type"] not in CtfIssueType:
            abort(400, message="题目类型错误")

        # 查询name是否存在
        qInfo = questionModel.CtfQuestion.query.filter_by(
            name=args["name"], user_id=self.user.id
        ).all()
        if qInfo:
            abort(400, message="试题名称重复")

        if args["vm_uuid"]:
            issue_belong = CtfIssueBelong.user_bind_vm
        else:
            issue_belong = CtfIssueBelong.user_upload

        try:
            obj = questionModel.CtfQuestion(
                name=args["name"],
                user_id=self.user.id,
                score=args["score"],
                type=args["type"],
                answer=args["answer"],
                describe=args["describe"],
                filepath=args.get("zip", None),
                belong=issue_belong,
                zipname = args.get("zipname", None)
            )
            db.session.add(obj)
            db.session.commit()
            # 记录日志
            current_app.logger.info(
                f"{self.user.username}---action--add--ctfquestion:" + args["name"]
            )
        except Exception as e:
            db.session().rollback()
            current_app.logger.exception(e)
            abort(400, message="保存试题失败")

        return {"code": 200, "data": "保存成功"}

    # 修改ctf问题
    def put(self, id=None):
        parser = reqparse.RequestParser()
        # 基本信息
        parser.add_argument("name", help="请填写试题名称", required=True)
        parser.add_argument("type", help="请填写试题类型", required=True, type=int)
        parser.add_argument("score", help="请填写试题分数", type=int)
        parser.add_argument("answer", help="请填写试题答案", required=True)
        parser.add_argument("describe", help="请填写试题描述", required=True)
        parser.add_argument("zip")
        parser.add_argument("zipname")
        args = parser.parse_args()

        if args["type"] not in CtfIssueType:
            abort(400, message="题目类型错误")

        q_info: questionModel.CtfQuestion = self.getQuestionInfo(id)
        if not q_info:
            abort(400, message="ctf试题信息错误")

        range_uuid_list = [item.range_uuid for item in q_info.bind_vm]
        if range_uuid_list:
            if db.exists(
                CtfRedBlue.query.filter(
                    CtfRedBlue.range_uuid.in_(range_uuid_list), CtfRedBlue.status != 2
                )
            ):
                abort(400, message="该赛题已绑定红蓝对抗赛，直到比赛结束前不允许编辑")
            ctf_list = Ctf.query.filter(
                Ctf.range_uuid.in_(range_uuid_list), Ctf.status != 2, Ctf.type == 1
            )
            issue_ids = [
                v.id
                for item in ctf_list
                for val in item.linkQuestion
                for v in val.question
            ]
            if q_info.id in issue_ids:
                abort(400, message="该赛题已绑定ctf比赛, 直到比赛结束前不允许编辑")

        # 重复校验 change： 21.12.28
        r = (
            questionModel.CtfQuestion.query.filter(
                questionModel.CtfQuestion.id != q_info.id
            )
            .filter_by(name=args["name"], user_id=self.user.id)
            .first()
        )
        if r:
            abort(400, message="课题名称重复")

        with db.auto_commit():
            q_info.name = args["name"]
            q_info.score = args["score"]
            q_info.type = args["type"]
            q_info.answer = args["answer"]
            q_info.describe = args["describe"]
            q_info.filepath = args.get("zip", None)
            q_info.zipname = args.get("zipname", None)
            db.session.add(q_info)

        current_app.logger.info(
            f"{self.user.username}---action--put--question id:{str(id)}"
        )

        return {"code": 200, "data": "保存成功"}

    def delete(self, id):
        q_info: CtfQuestion = self.getQuestionInfo(id)
        if not q_info:
            abort(400, message="参数错误")

        if q_info.belong in (CtfIssueBelong.system, CtfIssueBelong.system_bind_vm):
            abort(400, message="系统赛题及内置赛题无法删除")

        # 如果已绑定虚拟机则不允许删除
        if q_info.bind_vm.all():
            abort(400, message="该赛题已被虚拟机绑定，无法删除")
        # 如果已被ctf比赛绑定且比赛不是结束状态，也不允许删除
        elif q_info.CtfLinkQuestion and q_info.CtfLinkQuestion[0].Ctf.status != 2:
            abort(400, message="该赛题已绑定比赛且比赛未结束，无法删除")

        try:
            db.session.delete(q_info)
            db.session.commit()
        except Exception as e:
            current_app.logger.error("删除赛题失败", exc_info=e)
            db.session.rollback()
        else:
            return {"code": 200, "data": "删除成功"}


# ctf题库列表分页
class CtfQuestionList(Question):
    def get(self, notin=None):
        params = request.args
        page = params.get("page", 1)
        page_size = params.get("pageSize", 10)
        notin = params.get("notin")
        keyword = params.get("keyword")
        question_type = params.get("questionType")
        issue_type = params.get("issueType")
        try:
            page = int(page)
            page_size = int(page_size)
        except:  # noqa
            page = 1
            page_size = 10

        if page_size >= 50:
            abort(400, message="每页数量不能超过50")

        try:
            if question_type and int(question_type) in [
                CtfIssueBelong.system,
                CtfIssueBelong.system_bind_vm,
            ]:
                filters = [questionModel.CtfQuestion.belong == int(question_type)]
            else:
                filters = [
                    questionModel.CtfQuestion.belong.in_(
                        [CtfIssueBelong.system, CtfIssueBelong.system_bind_vm]
                    )
                ]
            if keyword:
                # 添加关键词的检索选项
                filters.append(questionModel.CtfQuestion.name.contains(keyword))

            if issue_type:
                filters = [questionModel.CtfQuestion.type == int(issue_type)]

            if notin:
                # 增加id过滤条件
                notinlist = [int(i) for i in notin.split(",") if i.isdigit()]
                paginate = (
                    questionModel.CtfQuestion.query.filter(*filters)
                    .filter(questionModel.CtfQuestion.id.notin_(notinlist))
                    .order_by(questionModel.CtfQuestion.id.desc())
                    .paginate(page, page_size, error_out=False)
                )
            else:
                paginate = (
                    questionModel.CtfQuestion.query.filter(*filters)
                    .order_by(questionModel.CtfQuestion.id.desc())
                    .paginate(page, page_size, error_out=False)
                )
            info = {"current_page": page, "data": []}
            if len(paginate.items) > 0:
                for content in paginate.items:
                    info["data"].append(
                        {
                            "id": content.id,
                            "name": content.name,
                            "answer": content.answer,
                            "score": content.score,
                            "type": content.type,
                            "zip": content.filepath,
                            "zipname": content.zipname,
                            "is_system": content.is_system,
                            "describe": content.describe,
                        }
                    )
            info = base_detail(info, paginate)
            return {"code": 200, "data": info}

        except Exception as e:
            current_app.logger.error("获取系统题库列表失败", exc_info=e)
            abort(400, message="获取系统题库列表失败")


class CtfMyQuestionList(Question):
    @staticmethod
    def _paginate(page, paginate):
        info = {"current_page": page, "data": []}
        if len(paginate.items) > 0:
            for content in paginate.items:
                info["data"].append(
                    {
                        "id": content.id,
                        "name": content.name,
                        "answer": content.answer,
                        "score": content.score,
                        "type": content.type,
                        "zip": content.filepath,
                        "zipname": content.zipname,
                        "is_system": content.is_system,
                        "describe": content.describe,
                    }
                )
        result = base_detail(info, paginate)
        return {"code": 200, "data": result}

    def get_list_to_show(self, keyword, page, page_size, **kwargs):
        """我的赛题列表中展示的虚拟内容"""
        question_type = kwargs.get("question_type")

        if question_type and int(question_type) in [
            CtfIssueBelong.user_upload,
            CtfIssueBelong.user_bind_vm,
        ]:
            filters = [
                CtfQuestion.user_id == self.user.id,
                CtfQuestion.belong == int(question_type),
            ]
        else:
            filters = [
                CtfQuestion.user_id == self.user.id,
                CtfQuestion.belong.in_(
                    [CtfIssueBelong.user_upload, CtfIssueBelong.user_bind_vm]
                ),
            ]
        if keyword:
            # 添加关键词的检索选项
            filters.append(CtfQuestion.name.contains(keyword))

        paginate = (
            CtfQuestion.query.filter(*filters)
            .order_by(CtfQuestion.id.desc())
            .paginate(page, page_size, error_out=False)
        )

        return self._paginate(page, paginate)

    def get_list_to_vm(self, keyword, page, page_size, range_uuid, vm_uuid):
        """获取用于绑定虚拟机的赛题"""
        range_manager = RangeManager(range_uuid=range_uuid)  # noqa
        range_vm = range_manager.get_vm_from_this(vm_uuid=vm_uuid)

        filters = []
        if keyword:
            # 添加关键词的检索选项
            filters.append(CtfQuestion.name.contains(keyword))

        # or过滤条件，默认可以获取到用户自己上传的或系统的
        or_filter = [
            and_(
                CtfQuestion.user_id == self.user.id,
                CtfQuestion.belong.in_(
                    [CtfIssueBelong.user_bind_vm, CtfIssueBelong.user_upload]
                ),
            ),
            and_(CtfQuestion.belong == CtfIssueBelong.system),
        ]

        # 如果该虚拟机存在内置绑定赛题，则增加内置赛题过滤条件
        system_vm_questions = [
            item.issue_id
            for item in IssueInitialBindRelation.query.filter_by(
                range_uuid=range_uuid, vm_uuid=vm_uuid
            )
        ]
        if system_vm_questions:
            or_filter.insert(0, CtfQuestion.id.in_(system_vm_questions))

        filters.append(or_(*or_filter))

        # 获取未绑定到该虚拟机的赛题
        vm_ids = [
            str(val.id) for val in range_manager.range_model.vmList if val != range_vm
        ]
        if vm_ids:
            not_bind_ids = [
                val
                for (val,) in db.session.execute(
                    f"""
                    SELECT
                        `a`.`id`
                    FROM
                        `api_ctf_question` AS `a`
                    WHERE (
                        SELECT count(*) FROM `api_vm_questions` AS `b`
                        WHERE `a`.`id` = `b`.`question_id` AND `b`.`vm_id` in ({",".join(vm_ids)})
                    ) = 0
                """
                )
            ]
            filters.insert(0, CtfQuestion.id.in_(not_bind_ids))

        # 虚拟机绑定时过滤所有答案为空的赛题
        filters.append(CtfQuestion.answer != "")

        paginate = (
            CtfQuestion.query.filter(*filters)
            .order_by(CtfQuestion.id.desc())
            .paginate(page, page_size, error_out=False)
        )

        return self._paginate(page, paginate)

    def get_list_to_ctf(self, keyword, page, page_size, range_uuid):
        """ctf创建比赛时提供赛题选择列表"""

        filters = []
        if keyword:
            # 添加关键词的检索选项
            filters.append(CtfQuestion.name.contains(keyword))

        if range_uuid:
            range_manager = RangeManager(range_uuid=range_uuid)
            bind_issue_ids = {
                val.id
                for item in range_manager.range_model.vmList
                for val in item.bind_question
            }
            if bind_issue_ids:
                filters.append(CtfQuestion.id.in_(bind_issue_ids))
            else:
                return {
                    "code": 200,
                    "data": {
                        "total": 0,
                        "pages": 0,
                        "prev_num": 0,
                        "next_num": 0,
                        "has_prev": False,
                        "has_next": False,
                        "data": [],
                    },
                }
        # 无场景返回系统公共和用户上传的
        else:
            filters.append(
                or_(
                    CtfQuestion.belong == CtfIssueBelong.system,
                    and_(
                        CtfQuestion.belong == CtfIssueBelong.user_upload,
                        CtfQuestion.user_id == self.user.id,
                    ),
                )
            )

        paginate = (
            CtfQuestion.query.filter(*filters)
            .order_by(CtfQuestion.id.desc())
            .paginate(page, page_size, error_out=False)
        )

        return self._paginate(page, paginate)

    def get(self):
        params = request.args
        page = params.get("page", 1, type=int)
        page_size = params.get("pageSize", 10, type=int)
        keyword = params.get("keyword")
        option = params.get("option", None)
        range_uuid = params.get("range_uuid", None)
        vm_uuid = params.get("vm_uuid", None)
        question_type = params.get("questionType")

        if page_size >= 50:
            abort(400, message="每页数量不能超过50")

        try:
            if option is None:
                return self.get_list_to_show(
                    keyword, page, page_size, question_type=question_type
                )
            elif option == "to_vm":
                if not vm_uuid or not range_uuid:
                    abort(400, message="缺少靶场或虚拟机ID")
                return self.get_list_to_vm(
                    keyword, page, page_size, range_uuid, vm_uuid
                )
            elif option == "to_ctf":
                return self.get_list_to_ctf(keyword, page, page_size, range_uuid)
        except HTTPException as e:
            raise e
        except Exception as e:
            current_app.logger.error("获取赛题列表失败", exc_info=e)
            abort(400, message="获取失败")
